package com.pocket.demo.business.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pocket.demo.base.api.PageResult;
import com.pocket.demo.business.controller.vo.HotelPageReqVO;
import com.pocket.demo.business.controller.vo.HotelSaveReqVO;
import com.pocket.demo.business.dao.HotelMapper;
import com.pocket.demo.business.po.Hotel;
import com.pocket.demo.business.service.HotelService;
import com.pocket.demo.core.initializing.SyncHotelBloomFilter;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

/**
 * @author cookie.
 **/
// 【demo-spring-cache-1.2】
@CacheConfig(cacheNames = "hotelCache")
@Service
public class HotelServiceImpl extends ServiceImpl<HotelMapper, Hotel> implements HotelService {

    private static final String HOTEL_LOCK_KEY = "hotelRWLock";

    @Resource
    RedissonClient redissonClient;
    @Resource
    RedisTemplate redisTemplate;

    @Override
    public void syncToElastic(){
        baseMapper.selectList(Wrappers.emptyWrapper(), resultContext -> {
            // 依次得到每条业务记录
            System.out.println("当前处理第" + resultContext.getResultCount() + "条记录.");
            Hotel hotel = resultContext.getResultObject();
        });
    }

    // 【demo-spring-cache-1.5】
    @CachePut(key = "#result.id", condition = "#result != null")
    @Override
    public Hotel add(HotelSaveReqVO hotelSaveReqVO) {
        Hotel hotel = BeanUtil.toBean(hotelSaveReqVO, Hotel.class);
        this.save(hotel);
        // TODO 双写一致性更好的方案，基于 canal 实现 redis 和 BloomFilter 数据的同步
        redissonClient.getBloomFilter(SyncHotelBloomFilter.BLOOM_FILTER_NAME).add(hotel.getId());
        return hotel;
    }

    /**
     * beforeInvocation：true - 方法执行前清除缓存；false（默认），方法执行成功后无异常才清除缓存
     * allEntries: true - 删除当前缓存Map所有缓存；false（默认） - 删除指定key的缓存
     */
    // 【demo-spring-cache-1.6】
    @CacheEvict(key = "#hotelId", condition = "#result > 0", beforeInvocation = false, allEntries = false)
    @Override
    public Integer delete(Integer hotelId) {
        if (hotelId == null){
            return 0;
        }
        return this.baseMapper.deleteById(hotelId);
    }

    // 【demo-spring-cache-1.5】
    @CachePut(key = "#hotelDO.id")
    @Override
    public Boolean update(HotelSaveReqVO hotelSaveReqVO) {
        Hotel hotel = BeanUtil.toBean(hotelSaveReqVO, Hotel.class);
        return this.updateById(hotel);
    }

    // 【demo-spring-cache-1.4】
    @Cacheable(key = "#hotelId", unless = "#result == null")
    @Override
    public Hotel getById(Integer hotelId) {
        if (hotelId == null){
            return null;
        }
        // 【demo-redis-3.2】
        // TODO 1 : 过滤器应该在redis缓存之前判断，这里用了 @Cacheable 。偷个懒。
        if (!redissonClient.getBloomFilter(SyncHotelBloomFilter.BLOOM_FILTER_NAME).contains(hotelId)){
            return null;
        }
        return this.baseMapper.selectById(hotelId);
    }

    @Override
    public PageResult<Hotel> queryByPage(HotelPageReqVO hotelPageReqVO) {
        LambdaQueryWrapper<Hotel> queryWrapper =  new LambdaQueryWrapper<Hotel>()
                .like(StringUtils.isNotBlank(hotelPageReqVO.getName()), Hotel::getName, hotelPageReqVO.getName())
                .like(StringUtils.isNotBlank(hotelPageReqVO.getBusiness()), Hotel::getBusiness, hotelPageReqVO.getBusiness())
                .eq(StringUtils.isNotBlank(hotelPageReqVO.getBrand()), Hotel::getBrand, hotelPageReqVO.getBrand())
                .eq(StringUtils.isNotBlank(hotelPageReqVO.getCity()), Hotel::getCity, hotelPageReqVO.getCity())
                .eq(StringUtils.isNotBlank(hotelPageReqVO.getStarName()), Hotel::getStarName, hotelPageReqVO.getStarName());
        return this.getBaseMapper().selectPage(hotelPageReqVO, hotelPageReqVO.getOrders(), queryWrapper);
    }


    /**
     * 带有 readLock 的缓存读 【demo-redis-5】
     * @param hotelId
     * @return
     */
    public Hotel getByIdWithLock(Integer hotelId){
        if (hotelId == null){
            return null;
        }
        Hotel hotel = null;
        RReadWriteLock rReadWriteLock = redissonClient.getReadWriteLock(HOTEL_LOCK_KEY);
        RLock readLock = rReadWriteLock.readLock();
        try {
            readLock.lock();
            hotel = (Hotel) redisTemplate.opsForValue().get(hotelId);
            if (hotel == null){
                hotel = this.baseMapper.selectById(hotelId);
                //redisTemplate.opsForValue().set(hotelId, hotelDO);
                redisTemplate.delete(hotelId);
            }
        } finally {
            readLock.unlock();
        }
        return hotel;
    }

    /**
     * 带有 writeLock 的更新 【demo-redis-5】
     * @param hotelSaveReqVO
     * @return
     */
    public Hotel updateWithLock(HotelSaveReqVO hotelSaveReqVO){
        Hotel hotel;
        RReadWriteLock rReadWriteLock = redissonClient.getReadWriteLock(HOTEL_LOCK_KEY);
        RLock writeLock = rReadWriteLock.writeLock();
        try {
            writeLock.lock();
            hotel = BeanUtil.toBean(hotelSaveReqVO, Hotel.class);
            this.updateById(hotel);
            redisTemplate.opsForValue().set(hotel.getId(), hotel);
        } finally {
            writeLock.unlock();
        }
        return hotel;
    }

}
